原文:Ascii art generator in Java
github源码地址
ASCII码字符画艺术是一种利用ASCII码标准中的可打印字符来产生视觉艺术效果的技术,它的存在在历史上是有意义的,当时的打印机还无法打印图片,而且当时在邮件中嵌入图像还无法实现,所以它也用于邮件中。在本文中,我将为你呈现一个用Java实现的、可以配置字体和对比度的ASCII码字符画生成器程序。因为这个程序是我在周末用几个小时搞定的,还不完美,但这是一个有意思的实验,在下面你可以看到实现代码,我将解释它的工作原理。
算法
算法的思路很简单。首先,我们将程序中要用到的每一个字符转化成一张图片,并缓存它。然后,我们遍历原始图像,对于每个字符大小的图片块,找出最佳匹配的字符。为了实现这一点,我们首先对原始图像做一些预处理:我们先将图像转化为灰度图,然后让其通过一个阈值滤波器,这样我们就得到一个黑白色的图像,我们可以将其与每个字符对比并计算差值。接着,对每个图片块选取最相似的字符,一直进行下去,直到整个图像都转换完成。此外,我们还可以根据需要调整阈值大小来调整对比度,增强最终的效果。
为了实现这一点,一个非常简单的方法是将红、绿、蓝的值都设置成三种颜色的平均值:
红=绿=蓝=(红+绿+蓝)/3
如果这个值低于阈值,我们就将它设置成白色,否则我们将其设置成黑色。最后,我们以像素为单位将图像与每个字符进行比较并计算出平均误差。如下面的图片和代码片段所示。
1 | int r1 = (charPixel >> 16) & 0xFF; |
因为颜色是存储在单个整数中,所以我们首先提取单个颜色成分并执行上面我解释过的计算,另一个挑战是准确地测量字符尺寸,并以它们为中心作图。在试验了多种方法之后,我最终发现这个比较好的方法:
1 | Rectangle rect = new TextLayout(Character.toString((char) i), fm.getFont(), |
你可以在Github上下载完整的源代码。
下面是一些使用不同字体尺寸和阈值的例子:
Part 2
由于上一篇博客谈到的ASCII码字符画生成器(在Github上查看源码)收到了很多反馈,我决定继续这个项目,如果大家很喜欢的话我再增加几个feature。我重新设计了程序的主要部分,让它更具扩展性,易于采用不同的算法,生成不同的输出等等。在这一部分,我会展示这个项目的新的架构,让你可以更容易地集成到自己的代码中,按照你的需要扩展它。
架构:
AsciiImgCache
在渲染ASCII码字符之前,实例化这个类是必要的。它将字体和字符数组作为参数,为每个字母生成图片,如果你不想麻烦,代码中有默认的字符数组。
假如你好奇:
1 | private static final char[] defaultCharacters = |
例子:
1 | // use only '/' '\' and ' ' |
BestCharacterFitStrategy
这个类是用来确定原图片与每个字符有多接近的算法的抽象。它有一个方法:
1 | float calculateError(final GrayscaleMatrix character, final GrayscaleMatrix tile); |
这个方法比较两张图片,返回一个浮点型的误差。每个字母都将与图片比较,最小误差的那个将被选中,返回。目前这个类中有两个可用的方法:ColorSquareErrorFitStrategy和 StructuralSimilarityFitStrategy。
ColorSquareErrorFitStrategy
这个很容易理解,它比较每一个像素点,计算每个灰度的均方差,用数学的语言来说就是:
$MSE=\frac{1}{n} \sum\limits_{1}\limits^n(C_i-T_i)^2$
n是像素点的个数,C和T分别是字符和分割的图片。
StructuralSimilarityFitStrategy
图像相似性指标(The structural similarity (SSIM) index algorithm)要求重现人类的感知,它的目标是提高类似与MSE的传统算法。我不会给出关于它机理的更多细节,如果你有兴趣,你可以在Wikipedia上面了解更多。我实验了一下,貌似实现了一个更优的版本。
AsciiConverter
这是算法的核心,它包括了所有分割原图片、匹配最佳字符的逻辑的实现。但是,它不包含输出ASCII字符画–它需要子类的实现。目前有两种实现:AsciiToImageConverter 和 AsciiToStringConverter,你可能猜到了,图片是用字符串输出产生的。
使用示例:
既然talk is cheap,我就展示一下产生ASCII码图片的大致流程:
1 | // initialize cache |
这有一些根据不同参数产生的实例图像:
原始图像
16磅字体,MSE
16磅字体,SSIM
3字符10磅字体,MSE
3字符10磅字体,SSIM
6磅字体,MSE
6磅字体,SSIM
进一步的工作
现在脑袋里有一些想法:
- 搜索并尝试更多的图像比较的算法
- 预处理图像,获得更好的结果(提高对比度,检测边缘等等)
- 并行地进行图像处理,提高性能,尝试一下看看是否需要
- 增加更多的转换结果(如html文件的输出)
- 增加多种颜色字符的输出
- 增加测试单元
如果你想改善代码,或者发现了代码的bug,在博客里评论或者到Github来贡献代码吧。